<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/iosre-1/">
<meta property="og:title" content="       Mach-O 文件格式探索 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       Mach-O 文件格式探索 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/iosre-1/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>Mach-O 文件格式探索</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-10-08"> 2017-10-08
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <p>最近开始研究 iOS 逆向的相关知识，并且使用 <a href="https://github.com/AloneMonkey/MonkeyDev/">MonkeyDev</a> 对 WeChat 进行了实战。这里我放出后期会持续更新的个人项目 <a href="https://github.com/Desgard/WeCheat">WeCheat</a>。在逆向专题真正开始之前，需要系统的学习一些软件内幕知识。这篇文章将从二进制格式开始讲起，并探秘 Mach-O 文件格式内容。</p>
<h2 id="section">进程与二进制格式</h2>
<p>进程在众多操作系统中都有提及，它是作为一个正在执行的程序的实例，这是 UNIX 的一个基本概念。而进程的出现是特殊文件在内存中加载得到的结果，这种文件必须使用操作系统可以认知的格式，这样才对该文件引入依赖库，初始化运行环境以及顺利地执行创造条件。</p>
<p><strong>Mach-O</strong>（Mach Object File Format）是 macOS 上的可执行文件格式，类似于 Linux 和大部分 UNIX 的原生格式 <strong>ELF</strong>（Extensible Firmware Interface）。为了更加全面的了解这块内容，我们看一下 macOS 支持的三种可执行格式：解释器脚本格式、通用二进制格式和 Mach-O 格式。</p>
<table>
  <thead>
    <tr>
      <th>可执行格式</th>
      <th>magic</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>脚本</td>
      <td><code>\x7FELF</code></td>
      <td>主要用于 shell 脚本，但是也常用语其他解释器，如 Perl, AWK 等。也就是我们常见的脚本文件中在 <code>#!</code> 标记后的字符串，即为执行命令的指令方式，以文件的 stdin 来传递命令</td>
    </tr>
    <tr>
      <td>通用二进制格式</td>
      <td><code>0xcafebabe</code> <br /><code>0xbebafeca</code></td>
      <td>包含多种架构支持的二进制格式，只在 macOS 上支持</td>
    </tr>
    <tr>
      <td>Mach-O</td>
      <td><code>0xfeedface</code>（32 位）<br /> <code>0xfeedfacf</code>（64 位）</td>
      <td>macOS 的原生二进制格式</td>
    </tr>
  </tbody>
</table>
<h3 id="universal-binary">通用二进制格式（Universal Binary）</h3>
<p>这个格式在有些资料中也叫胖二进制格式（Fat Binary），Apple 提出这个概念是为了解决一些历史原因，macOS（更确切的应该说是 OS X）最早是构建于 PPC 架构之上，后来才移植到 Intel 架构（从 Mac OS X Tiger 10.4.7 开始），通用二进制格式的二进制文件可以在 PPC 和 x86 两种处理器上执行。</p>
<p>说到底，通用二进制格式只不过是对多架构的二进制文件的打包集合文件，而 macOS 中的多架构二进制文件也就是适配不同架构的 Mach-O 文件。</p>
<p><strong>Fat Header</strong> 的数据结构在 <code>&lt;mach-o/fat.h&gt;</code> 头文件中有定义，可以参看 <code>/usr/include/mach-o/fat.h</code> 找到定义头：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define FAT_MAGIC	0xcafebabe</span>
<span class="c1">#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */</span>
<span class="n">struct</span> <span class="n">fat_header</span> <span class="p">{</span>
	<span class="n">uint32_t</span>	<span class="n">magic</span><span class="p">;</span>		<span class="sr">/* FAT_MAGIC 或 FAT_MAGIC_64 */</span>
	<span class="n">uint32_t</span>	<span class="n">nfat_arch</span><span class="p">;</span>	<span class="sr">/* 结构体实例的个数 */</span>
<span class="p">};</span>
<span class="n">struct</span> <span class="n">fat_arch</span> <span class="p">{</span>
	<span class="n">cpu_type_t</span>	<span class="n">cputype</span><span class="p">;</span>	<span class="sr">/* cpu 说明符 (int) */</span>
	<span class="n">cpu_subtype_t</span>	<span class="n">cpusubtype</span><span class="p">;</span>	<span class="sr">/* 指定 cpu 确切型号的整数 (int) */</span>
	<span class="n">uint32_t</span>	<span class="n">offset</span><span class="p">;</span>		<span class="sr">/* CPU 架构数据相对于当前文件开头的偏移值 */</span>
	<span class="n">uint32_t</span>	<span class="n">size</span><span class="p">;</span>		<span class="sr">/* 数据大小 */</span>
	<span class="n">uint32_t</span>	<span class="n">align</span><span class="p">;</span>		<span class="sr">/* 数据内润对其边界，取值为 2 的幂 */</span>
<span class="p">};</span></code></pre></div>
<p>对于 <code>cputype</code> 和 <code>cpusubtype</code> 两个字段这里不讲述，可以参看 <code>/usr/include/mach/machine.h</code> 头中对其的定义，另外 <a href="https://developer.apple.com/documentation/kernel/mach_header?language=objc">Apple 官方文档</a>中也有简单的描述。</p>
<p>在 <code>fat_header</code> 中，<code>magic</code> 也就是我们之前在表中罗列的 <em>magic</em> 标识符，也可以类比成 UNIX 中 ELF 文件的 <em>magic</em> 标识。加载器会通过这个符号来判断这是什么文件，通用二进制的 <em>magic</em> 为 <code>0xcafebabe</code>。<code>nfat_arch</code> 字段指明当前的通用二进制文件中包含了多少个不同架构的 Mach-O 文件。<code>fat_header</code> 后会跟着多个 <code>fat_arch</code>，并与多个 Mach-O 文件及其描述信息（文件大小、CPU 架构、CPU 型号、内存对齐方式）相关联。</p>
<p>这里可以通过 <code>file</code> 命令来查看简要的架构信息，这里以 iOS 平台 WeChat 4.5.1 版本为例：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">~</span> <span class="n">file</span> <span class="no">Desktop</span><span class="o">/</span><span class="no">WeChat</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="no">WeChat</span>
<span class="no">Desktop</span><span class="o">/</span><span class="no">WeChat</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="ss">WeChat</span><span class="p">:</span> <span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="n">universal</span> <span class="n">binary</span> <span class="n">with</span> <span class="mi">2</span> <span class="ss">architectures</span><span class="p">:</span> <span class="o">[</span><span class="ss">arm_v7</span><span class="p">:</span> <span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="n">executable</span> <span class="n">arm_v7</span><span class="o">]</span> <span class="o">[</span><span class="n">arm64</span><span class="o">]</span>
<span class="no">Desktop</span><span class="o">/</span><span class="no">WeChat</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="no">WeChat</span> <span class="p">(</span><span class="k">for</span> <span class="n">architecture</span> <span class="n">armv7</span><span class="p">):</span>	<span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="n">executable</span> <span class="n">arm_v7</span>
<span class="no">Desktop</span><span class="o">/</span><span class="no">WeChat</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="no">WeChat</span> <span class="p">(</span><span class="k">for</span> <span class="n">architecture</span> <span class="n">arm64</span><span class="p">):</span>	<span class="no">Mach</span><span class="o">-</span><span class="n">O</span> <span class="mi">64</span><span class="o">-</span><span class="n">bit</span> <span class="n">executable</span> <span class="n">arm64</span></code></pre></div>
<p>进一步，也可以使用 <code>otool</code> 工具来打印其 <code>fat_header</code> 详细信息：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">~</span> <span class="n">otool</span> <span class="o">-</span><span class="n">f</span> <span class="o">-</span><span class="n">V</span> <span class="no">Desktop</span><span class="o">/</span><span class="no">WeChat</span><span class="o">.</span><span class="n">app</span><span class="o">/</span><span class="no">WeChat</span>
<span class="no">Fat</span> <span class="n">headers</span>
<span class="n">fat_magic</span> <span class="no">FAT_MAGIC</span>
<span class="n">nfat_arch</span> <span class="mi">2</span>
<span class="n">architecture</span> <span class="n">armv7</span>
    <span class="n">cputype</span> <span class="no">CPU_TYPE_ARM</span>
    <span class="n">cpusubtype</span> <span class="no">CPU_SUBTYPE_ARM_V7</span>
    <span class="n">capabilities</span> <span class="mh">0x0</span>
    <span class="n">offset</span> <span class="mi">16384</span>
    <span class="n">size</span> <span class="mi">56450224</span>
    <span class="n">align</span> <span class="mi">2</span><span class="o">^</span><span class="mi">14</span> <span class="p">(</span><span class="mi">16384</span><span class="p">)</span>
<span class="n">architecture</span> <span class="n">arm64</span>
    <span class="n">cputype</span> <span class="no">CPU_TYPE_ARM64</span>
    <span class="n">cpusubtype</span> <span class="no">CPU_SUBTYPE_ARM64_ALL</span>
    <span class="n">capabilities</span> <span class="mh">0x0</span>
    <span class="n">offset</span> <span class="mi">56475648</span>
    <span class="n">size</span> <span class="mi">64571648</span>
    <span class="n">align</span> <span class="mi">2</span><span class="o">^</span><span class="mi">14</span> <span class="p">(</span><span class="mi">16384</span><span class="p">)</span></code></pre></div>
<p>之后我们用 <em>Synalyze It!</em> 来查看 WeChat 的 Mach64 Header 的效果：</p>
<p><img src="../assets/images/blog/15058343519881/15073563641200.jpg" alt="" /></p>
<ul>
  <li>从第一个段中得到 <code>magic = 0xcafebabe</code> ，说明是 <code>FAT_MAGIC</code>。</li>
  <li>第二段中所存储的字段为 <code>nfat_arch = 0x00000002</code>，说明该 App 中包含了两种 CPU 架构。</li>
  <li>后续的则是 <code>fat_arch</code> 结构体中的内容，<code>cputype(0x0000000c)</code>、<code>cpusubtype(0x00000009)</code>、<code>offset(0x00004000)</code>、<code>size(0x03505C00)</code> 等等。需要臧帅闯是如果只含有一种 CPU 架构，是没有 fat 头定义的，这部分则可跳过，从而直接过去 <code>arch</code> 数据。</li>
</ul>
<h2 id="mach-o-">Mach-O 文件格式</h2>
<p>由上所知一个通用二进制格式包含了很多个 Mach-O 文件格式，下面我们来具体说说这个格式。Mach-O 文件格式在官方文档中有一个描述图，是很多教程中都引用到的，我重新绘制了一版更清晰的：</p>
<p><img src="../assets/images/blog/15058343519881/mach-o.png" alt="mach-o" />
可以看的出 Mach-O 主要由 3 部分组成:</p>
<ul>
  <li>Mach-O 头（Mach Header）：这里描述了 Mach-O 的 CPU 架构、文件类型以及加载命令等信息；</li>
  <li>加载命令（Load Command）：描述了文件中数据的具体组织结构，不同的数据类型使用不同的加载命令表示；</li>
  <li>数据区（Data）：Data 中每一个段（Segment）的数据都保存在此，段的概念和 ELF 文件中段的概念类似，都拥有一个或多个 Section ，用来存放数据和代码。</li>
</ul>
<h3 id="mach-o--1">Mach-O 头</h3>
<p>与 Mach-O 文件格式有关的结构体定义都可以从 <code>/usr/include/mach-o/loader.h</code> 中找到，也就是 <code>&lt;mach-o/loader.h&gt;</code> 头。以下只给出 64 位定义的代码，因为 32 位的区别是缺少了一个预留字段：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define	MH_MAGIC	0xfeedface	/* the mach magic number */</span>
<span class="c1">#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */</span>
<span class="n">struct</span> <span class="n">mach_header_64</span> <span class="p">{</span>
	<span class="n">uint32_t</span>	<span class="n">magic</span><span class="p">;</span>		<span class="sr">/* mach magic 标识符 */</span>
	<span class="n">cpu_type_t</span>	<span class="n">cputype</span><span class="p">;</span>	<span class="sr">/* CPU 类型标识符，同通用二进制格式中的定义 */</span>
	<span class="n">cpu_subtype_t</span>	<span class="n">cpusubtype</span><span class="p">;</span>	<span class="sr">/* CPU 子类型标识符，同通用二级制格式中的定义 */</span>
	<span class="n">uint32_t</span>	<span class="n">filetype</span><span class="p">;</span>	<span class="sr">/* 文件类型 */</span>
	<span class="n">uint32_t</span>	<span class="n">ncmds</span><span class="p">;</span>		<span class="sr">/* 加载器中加载命令的条数 */</span>
	<span class="n">uint32_t</span>	<span class="n">sizeofcmds</span><span class="p">;</span>	<span class="sr">/* 加载器中加载命令的总大小 */</span>
	<span class="n">uint32_t</span>	<span class="n">flags</span><span class="p">;</span>		<span class="sr">/* dyld 的标志 */</span>
	<span class="n">uint32_t</span>	<span class="n">reserved</span><span class="p">;</span>	<span class="sr">/* 64 位的保留字段 */</span>
<span class="p">};</span></code></pre></div>
<p>由于 Mach-O 支持多种类型文件，所以此处引入了 <code>filetype</code> 字段来标明，这些文件类型定义在 <code>loader.h</code> 文件中同样可以找到。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define	MH_OBJECT	0x1		/* Target 文件：编译器对源码编译后得到的中间结果 */</span>
<span class="c1">#define	MH_EXECUTE	0x2		/* 可执行二进制文件 */</span>
<span class="c1">#define	MH_FVMLIB	0x3		/* VM 共享库文件（还不清楚是什么东西） */</span>
<span class="c1">#define	MH_CORE		0x4		/* Core 文件，一般在 App Crash 产生 */</span>
<span class="c1">#define	MH_PRELOAD	0x5		/* preloaded executable file */</span>
<span class="c1">#define	MH_DYLIB	0x6		/* 动态库 */</span>
<span class="c1">#define	MH_DYLINKER	0x7		/* 动态连接器 /usr/lib/dyld */</span>
<span class="c1">#define	MH_BUNDLE	0x8		/* 非独立的二进制文件，往往通过 gcc-bundle 生成 */</span>
<span class="c1">#define	MH_DYLIB_STUB	0x9		/* 静态链接文件（还不清楚是什么东西） */</span>
<span class="c1">#define	MH_DSYM		0xa		/* 符号文件以及调试信息，在解析堆栈符号中常用 */</span>
<span class="c1">#define	MH_KEXT_BUNDLE	0xb		/* x86_64 内核扩展 */</span></code></pre></div>
<p>另外在 <code>loader.h</code> 中还可以找到 <code>flags</code> 中所取值的全部定义，这里只介绍常用的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define	MH_NOUNDEFS	0x1		/* Target 文件中没有带未定义的符号，常为静态二进制文件 */</span>
<span class="c1">#define MH_SPLIT_SEGS	0x20  /* Target 文件中的只读 Segment 和可读写 Segment 分开  */</span>
<span class="c1">#define MH_TWOLEVEL	0x80		/* 该 Image 使用二级命名空间(two name space binding)绑定方案 */</span>
<span class="c1">#define MH_FORCE_FLAT	0x100 /* 使用扁平命名空间(flat name space binding)绑定（与 MH_TWOLEVEL 互斥） */</span>
<span class="c1">#define MH_WEAK_DEFINES	0x8000 /* 二进制文件使用了弱符号 */</span>
<span class="c1">#define MH_BINDS_TO_WEAK 0x10000 /* 二进制文件链接了弱符号 */</span>
<span class="c1">#define MH_ALLOW_STACK_EXECUTION 0x20000/* 允许 Stack 可执行 */</span>
<span class="c1">#define	MH_PIE 0x200000  /* 对可执行的文件类型启用地址空间 layout 随机化 */</span>
<span class="c1">#define MH_NO_HEAP_EXECUTION 0x1000000 /* 将 Heap 标记为不可执行，可防止 heap spray 攻击 */</span></code></pre></div>
<p>Mach-O 文件头主要目的是为加载命令提供信息。加载命令过程紧跟在头之后，并且 <code>ncmds</code> 和 <code>sizeofcmds</code> 来能个字段将会用在加载命令的过程中。</p>
<h3 id="mach-o-data">Mach-O Data</h3>
<p>加载命令在 Mach-O 文件加载解析时，会被内核加载器或者动态链接器调用。这些指令都采用 <code>Type-Size-Value</code> 这种格式，即：32 位的 <code>cmd</code> 值（表示类型），32 位的 <code>cmdsize</code> 值（32 位二级制位 4 的倍数，64 位位 8 的倍数），以及命令本身（由 <code>cmdsize</code> 指定的长度）。内核加载器使用的命令可以参看 <a href="http://unix.superglobalmegacorp.com/xnu/newsrc/bsd/kern/mach_loader.c.html">xnu 源码</a>来学习，其他命令则是由动态链接器处理的。</p>
<p>在正式进入加载命令这一过程之前，先来学习一下 Mach-O 的 Data 区域，其中由 Segment 段和 Section 节组成。先来说 Segment 的组成，以下代码仍旧来自 <code>loader.h</code>：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1">#define	SEG_PAGEZERO	&quot;__PAGEZERO&quot; /* 当时 MH_EXECUTE 文件时，捕获到空指针 */</span>
<span class="c1">#define	SEG_TEXT	&quot;__TEXT&quot; /* 代码/只读数据段 */</span>
<span class="c1">#define	SEG_DATA	&quot;__DATA&quot; /* 数据段 */</span>
<span class="c1">#define	SEG_OBJC	&quot;__OBJC&quot; /* Objective-C runtime 段 */</span>
<span class="c1">#define	SEG_LINKEDIT	&quot;__LINKEDIT&quot; /* 包含需要被动态链接器使用的符号和其他表，包括符号表、字符串表等 */</span></code></pre></div>
<p>进而来看一下 Segment 的数据结构具体是什么样的（同样这里也只放出 64 位的代码，与 32 位的区别就是其中 <code>uint64_t</code> 类型的几个字段取代了原先 32 位类型字段）：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">struct</span> <span class="n">segment_command_64</span> <span class="p">{</span> 
	<span class="n">uint32_t</span>	<span class="n">cmd</span><span class="p">;</span>		<span class="sr">/* LC_SEGMENT_64 */</span>
	<span class="n">uint32_t</span>	<span class="n">cmdsize</span><span class="p">;</span>	<span class="sr">/* section_64 结构体所需要的空间 */</span>
	<span class="n">char</span>		<span class="n">segname</span><span class="o">[</span><span class="mi">16</span><span class="o">]</span><span class="p">;</span>	<span class="sr">/* segment 名字，上述宏中的定义 */</span>
	<span class="n">uint64_t</span>	<span class="n">vmaddr</span><span class="p">;</span>		<span class="sr">/* 所描述段的虚拟内存地址 */</span>
	<span class="n">uint64_t</span>	<span class="n">vmsize</span><span class="p">;</span>		<span class="sr">/* 为当前段分配的虚拟内存大小 */</span>
	<span class="n">uint64_t</span>	<span class="n">fileoff</span><span class="p">;</span>	<span class="sr">/* 当前段在文件中的偏移量 */</span>
	<span class="n">uint64_t</span>	<span class="n">filesize</span><span class="p">;</span>	<span class="sr">/* 当前段在文件中占用的字节 */</span>
	<span class="n">vm_prot_t</span>	<span class="n">maxprot</span><span class="p">;</span>	<span class="sr">/* 段所在页所需要的最高内存保护，用八进制表示 */</span>
	<span class="n">vm_prot_t</span>	<span class="n">initprot</span><span class="p">;</span>	<span class="sr">/* 段所在页原始内存保护 */</span>
	<span class="n">uint32_t</span>	<span class="n">nsects</span><span class="p">;</span>		<span class="sr">/* 段中 Section 数量 */</span>
	<span class="n">uint32_t</span>	<span class="n">flags</span><span class="p">;</span>		<span class="sr">/* 标识符 */</span>
<span class="p">};</span></code></pre></div>
<p>部分的 Segment （主要指的 <code>__TEXT</code> 和 <code>__DATA</code>）可以进一步分解为 Section。之所以按照 Segment -&gt; Section 的结构组织方式，是因为在同一个 Segment 下的 Section，可以控制相同的权限，也可以不完全按照 Page 的大小进行内存对其，节省内存的空间。而 Segment 对外整体暴露，在程序载入阶段映射成一个完整的虚拟内存，更好的做到内存对齐（可以继续参考 <em>OS X &amp; iOS Kernel Programming</em> 一书的第一章内容）。下面给出 Section 具体的数据结构：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">struct</span> <span class="n">section_64</span> <span class="p">{</span> 
	<span class="n">char</span>		<span class="n">sectname</span><span class="o">[</span><span class="mi">16</span><span class="o">]</span><span class="p">;</span>	<span class="sr">/* Section 名字 */</span>
	<span class="n">char</span>		<span class="n">segname</span><span class="o">[</span><span class="mi">16</span><span class="o">]</span><span class="p">;</span>	<span class="sr">/* Section 所在的 Segment 名称 */</span>
	<span class="n">uint64_t</span>	<span class="n">addr</span><span class="p">;</span>		<span class="sr">/* Section 所在的内存地址 */</span>
	<span class="n">uint64_t</span>	<span class="n">size</span><span class="p">;</span>		<span class="sr">/* Section 的大小 */</span>
	<span class="n">uint32_t</span>	<span class="n">offset</span><span class="p">;</span>		<span class="sr">/* Section 所在的文件偏移 */</span>
	<span class="n">uint32_t</span>	<span class="n">align</span><span class="p">;</span>		<span class="sr">/* Section 的内存对齐边界 (2 的次幂) */</span>
	<span class="n">uint32_t</span>	<span class="n">reloff</span><span class="p">;</span>		<span class="sr">/* 重定位信息的文件偏移 */</span>
	<span class="n">uint32_t</span>	<span class="n">nreloc</span><span class="p">;</span>		<span class="sr">/* 重定位条目的数目 */</span>
	<span class="n">uint32_t</span>	<span class="n">flags</span><span class="p">;</span>		<span class="sr">/* 标志属性 */</span>
	<span class="n">uint32_t</span>	<span class="n">reserved1</span><span class="p">;</span>	<span class="sr">/* 保留字段1 (for offset or index) */</span>
	<span class="n">uint32_t</span>	<span class="n">reserved2</span><span class="p">;</span>	<span class="sr">/* 保留字段2 (for count or sizeof) */</span>
	<span class="n">uint32_t</span>	<span class="n">reserved3</span><span class="p">;</span>	<span class="sr">/* 保留字段3 */</span>
<span class="p">};</span></code></pre></div>
<p>下面列举一些常见的 Section。</p>
<table>
  <thead>
    <tr>
      <th>Section</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>__TEXT.__text</code></td>
      <td>主程序代码</td>
    </tr>
    <tr>
      <td><code>__TEXT.__cstring</code></td>
      <td>C 语言字符串</td>
    </tr>
    <tr>
      <td><code>__TEXT.__const</code></td>
      <td><code>const</code> 关键字修饰的常量</td>
    </tr>
    <tr>
      <td><code>__TEXT.__stubs	</code></td>
      <td>用于 Stub 的占位代码，很多地方称之为<em>桩代码</em>。</td>
    </tr>
    <tr>
      <td><code>__TEXT.__stubs_helper</code></td>
      <td>当 Stub 无法找到真正的符号地址后的最终指向</td>
    </tr>
    <tr>
      <td><code>__TEXT.__objc_methname</code></td>
      <td>Objective-C 方法名称</td>
    </tr>
    <tr>
      <td><code>__TEXT.__objc_methtype</code></td>
      <td>Objective-C 方法类型</td>
    </tr>
    <tr>
      <td><code>__TEXT.__objc_classname</code></td>
      <td>Objective-C 类名称</td>
    </tr>
    <tr>
      <td><code>__DATA.__data</code></td>
      <td>初始化过的可变数据</td>
    </tr>
    <tr>
      <td><code>__DATA.__la_symbol_ptr</code></td>
      <td>lazy binding 的指针表，表中的指针一开始都指向 <code>__stub_helper</code></td>
    </tr>
    <tr>
      <td><code>__DATA.nl_symbol_ptr</code></td>
      <td>非 lazy binding 的指针表，每个表项中的指针都指向一个在装载过程中，被动态链机器搜索完成的符号</td>
    </tr>
    <tr>
      <td><code>__DATA.__const</code></td>
      <td>没有初始化过的常量</td>
    </tr>
    <tr>
      <td><code>__DATA.__cfstring</code></td>
      <td>程序中使用的 Core Foundation 字符串（<code>CFStringRefs</code>）</td>
    </tr>
    <tr>
      <td><code>__DATA.__bss</code></td>
      <td>BSS，存放为初始化的全局变量，即常说的静态内存分配</td>
    </tr>
    <tr>
      <td><code>__DATA.__common</code></td>
      <td>没有初始化过的符号声明</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_classlist</code></td>
      <td>Objective-C 类列表</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_protolist</code></td>
      <td>Objective-C 原型</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_imginfo</code></td>
      <td>Objective-C 镜像信息</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_selfrefs</code></td>
      <td>Objective-C <code>self</code> 引用</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_protorefs</code></td>
      <td>Objective-C 原型引用</td>
    </tr>
    <tr>
      <td><code>__DATA.__objc_superrefs</code></td>
      <td>Objective-C 超类引用</td>
    </tr>
  </tbody>
</table>
<h2 id="section-1">验证实验</h2>
<p>当了解了 Segment 和 Section 的定义之后，我们可以简单的探索一下 <code>LC_SEGMENT</code> 这个命令的过程。用 helloworld 来做个试验：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="sr">//</span> <span class="n">main</span><span class="o">.</span><span class="n">cpp</span>
<span class="c1">#import &lt;stdio.h&gt;</span>
<span class="n">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nb">printf</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>使用 <code>clang -g main.cpp -o main</code> 生成执行文件。然后拖入到 <em>MachOView</em> 中来查看一下加载 Segment 的结构（当然使用 <em>Synalyze It!</em> 也能捕捉到这些信息的，但是 <em>MachOView</em> 更对结构的分层更加一目了然）：</p>
<p><img src="../assets/images/blog/15058343519881/15074302081133.jpg" alt="MachOView" /></p>
<p><img src="../assets/images/blog/15058343519881/15074291855917.jpg" alt="Synalyze It! 分析结果" /></p>
<p>在 <code>LC_SEGMENT_64</code> 中有四个元素，分别是 <code>__PAGEZERO</code>、<code>__TEXT</code>、<code>__DATA</code>、<code>__LINKEDIT</code> 这四个 Segment。其中，<code>__TEXT</code> 的 <code>__text</code> Section 的加载是可以验证到的，我们从 Section 的实例中取出其 <code>addr</code> 来对比汇编之后代码的起始地址即可。使用 <code>otool -vt main</code> 来获取其汇编代码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">~</span> <span class="n">otool</span> <span class="o">-</span><span class="n">vt</span> <span class="n">main</span>
<span class="ss">main</span><span class="p">:</span>
<span class="p">(</span><span class="n">__TEXT</span><span class="p">,</span><span class="n">__text</span><span class="p">)</span> <span class="n">section</span>
<span class="ss">_main</span><span class="p">:</span>
<span class="mo">0000000100000</span><span class="n">f60</span>	<span class="n">pushq</span>	<span class="o">%</span><span class="n">rbp</span>
<span class="mo">0000000100000</span><span class="n">f61</span>	<span class="n">movq</span>	<span class="o">%</span><span class="n">rsp</span><span class="p">,</span> <span class="o">%</span><span class="n">rbp</span>
<span class="mo">0000000100000</span><span class="n">f64</span>	<span class="n">subq</span>	<span class="vg">$0x10</span><span class="p">,</span> <span class="o">%</span><span class="n">rsp</span>
<span class="mo">0000000100000</span><span class="n">f68</span>	<span class="n">leaq</span>	<span class="mh">0x3b</span><span class="p">(</span><span class="o">%</span><span class="n">rip</span><span class="p">),</span> <span class="o">%</span><span class="n">rdi</span>
<span class="mo">0000000100000</span><span class="n">f6f</span>	<span class="n">movl</span>	<span class="vg">$0x0</span><span class="p">,</span> <span class="o">-</span><span class="mh">0x4</span><span class="p">(</span><span class="o">%</span><span class="n">rbp</span><span class="p">)</span>
<span class="mo">0000000100000</span><span class="n">f76</span>	<span class="n">movb</span>	<span class="vg">$0x0</span><span class="p">,</span> <span class="o">%</span><span class="n">al</span>
<span class="mo">0000000100000</span><span class="n">f78</span>	<span class="n">callq</span>	<span class="mh">0x100000f8a</span>
<span class="mo">0000000100000</span><span class="n">f7d</span>	<span class="n">xorl</span>	<span class="o">%</span><span class="n">ecx</span><span class="p">,</span> <span class="o">%</span><span class="n">ecx</span>
<span class="mo">0000000100000</span><span class="n">f7f</span>	<span class="n">movl</span>	<span class="o">%</span><span class="n">eax</span><span class="p">,</span> <span class="o">-</span><span class="mh">0x8</span><span class="p">(</span><span class="o">%</span><span class="n">rbp</span><span class="p">)</span>
<span class="mo">0000000100000</span><span class="n">f82</span>	<span class="n">movl</span>	<span class="o">%</span><span class="n">ecx</span><span class="p">,</span> <span class="o">%</span><span class="n">eax</span>
<span class="mo">0000000100000</span><span class="n">f84</span>	<span class="n">addq</span>	<span class="vg">$0x10</span><span class="p">,</span> <span class="o">%</span><span class="n">rsp</span>
<span class="mo">0000000100000</span><span class="n">f88</span>	<span class="n">popq</span>	<span class="o">%</span><span class="n">rbp</span>
<span class="mo">0000000100000</span><span class="n">f89</span>	<span class="n">retq</span></code></pre></div>
<p>对比 <em>Synalyze It!</em> 的分析结果中 <code>SEG__TEXT.__text</code> 中的 <code>addr</code> 观察：</p>
<p><img src="../assets/images/blog/15058343519881/15074350810612.jpg" alt="" /></p>
<p>其对应的物理地址均为 <code>0x100000F60</code>，说明其 <code>LC_SEGMENT</code> 对于 Segment 和 Section 的加载与我们的预期完全一致。</p>
<h2 id="textstubs-">对于 <code>__TEXT.__stubs</code> 的一些探究</h2>
<p>这是对于五子棋大神的 <a href="http://satanwoo.github.io/2017/06/13/Macho-1/"><em>深入剖析Macho (1)</em></a> 中的过程进行再次验证。在查阅过关于 <code>__stubs</code> 的相关资料后还是不太理解到底是个什么东西。在知乎中有这么一个<a href="https://www.zhihu.com/question/24844900">问题</a>，其中的观点是 Stub 会根据不同的代码上下文表示的含义不同。在 <a href="https://en.wikipedia.org/wiki/Method_stub">wikipedia</a> 也有一个关于 <a href="https://en.wikipedia.org/wiki/Method_stub">Method stub</a> 的词条，其中的解释是这样的：</p>
<blockquote>
  <p>A method stub or simply stub in software development is a piece of code used to stand in for some other programming functionality. A stub may simulate the behavior of existing code (such as a procedure on a remote machine) or be a temporary substitute for yet-to-be-developed code. Stubs are therefore most useful in porting, distributed computing as well as general software development and testing.</p>
</blockquote>
<p>大意就是：<em>Stub 是指用来替换一部分功能的程序段。桩程序可以用来模拟已有程序的行为（比如一个远端机器的过程）或是对将要开发的代码的一种临时替代。</em></p>
<p>我们将 Calculator 应用拖入到 <em>Synalyze It!</em> 和 <em>Hopper Disassembler</em> 中。首先使用 <em>Synalyze It!</em> 来查找一个 <code>__stubs</code> 地址：</p>
<p><img src="../assets/images/blog/15058343519881/15074371126384.jpg" alt="" /></p>
<p>取出地址 <code>0x100016450</code> 并在 <em>Hopper</em> 中查找对应的代码，并以此双击进入：</p>
<p><img src="../assets/images/blog/15058343519881/15074373726740.jpg" alt="" /></p>
<p><img src="../assets/images/blog/15058343519881/15074375282210.jpg" alt="" /></p>
<p>到达第二幅图的位置的时候，我们发现无法继续进入，因为 <code>_CFRelease</code> 中的代码是没有意义的。我们拿出 <code>0x100031000</code> 这个首地址，在 <em>MachOView</em> 中查找：</p>
<p><img src="../assets/images/blog/15058343519881/15074383379688.jpg" alt="" /></p>
<p>发现其低 32 位的值为 <code>0x00000010001663E</code>，将这个地址继续在 <em>hopper</em> 中搜索：</p>
<p><img src="../assets/images/blog/15058343519881/15074385597132.jpg" alt="" /></p>
<p>发现这一系列操作都会跳到 <code>0x000000010001650c</code> 这个位置，而这里就是 <code>__TEXT.__stub_helper</code> 的表头。</p>
<p><img src="../assets/images/blog/15058343519881/15074386833986.jpg" alt="" /></p>
<p>也就是说，<code>__la_symbol_ptr</code> 里面的所有表项的数据在开始时都会被 binding 成 <code>__stub_helper</code>。而在之后的调用中，虽然依旧会跳到 <code>__stub</code> 区域，但是 <code>__la_symbol_ptr</code> 中由于在之前的调用中获取到了对应方法的真实地址，所以无需在进入 <em>dyld_stub_binder</em> 阶段，并直接调用函数。这样就完成了一次近似于 <strong>lazy</strong> 思想的延时 binding 过程。（这个过程可以用 lldb 来加以验证，在之后会补充。）</p>
<p>总结一下 Stub 机制。其实和 <code>wikipedia</code> 上的说法一致，设置函数占位符并采用 <strong>lazy</strong> 思想做成延迟 binding 的流程。在 macOS 中也是如此，外部函数引用在 <code>__DATA</code> 段的 <code>__la_symbol_ptr</code> 区域先生产一个占位符，当第一个调用启动时，就会进入符号的动态链接过程，一旦找到地址后，就将 <code>__DATA</code> Segment 中的 <code>__la_symbol_ptr</code> Section 中的占位符修改为方法的真实地址，这样就完成了只需要一个符号绑定的执行过程。</p>
<h2 id="section-2">参考文献</h2>
<ul>
  <li><a href="http://satanwoo.github.io/2017/06/13/Macho-1/">深入剖析MachO - satanwoo 五子棋</a></li>
  <li><a href="http://blog.tingyun.com/web/article/detail/1341?spm=5176.100239.blogcont64288.7.IuEYnv">Mach-O二进制文件解析 - 刘振天</a></li>
  <li><a href="http://oncenote.com/2015/06/01/How-App-Launch/">由App的启动说起 - Jamin’s Blog</a></li>
  <li><a href="https://makezl.github.io/2016/06/27/dylib/">dylib浅析 - leisuro 的村屋</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/24858664">Mach-O文件格式 - 非虫</a></li>
</ul>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《Mach-O 文件格式探索》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/iosre-1/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/iosre-1/&amp;title=《Mach-O 文件格式探索》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/iosre-1/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/iosre-1/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>